package com.zhaokuner.parentstarter.utils;

import org.springframework.cglib.reflect.FastClass;
import org.springframework.cglib.reflect.FastMethod;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * bean的getter、setter反射工具
 *
 * @author 赵希坤
 */
public class BeanReflectUtil {

    /**
     * feild处理
     *
     * @param <T>
     */
    @FunctionalInterface
    public interface EachField<T> {
        /**
         * 处理
         *
         * @param name
         * @param value
         * @param t
         */
        void deal(String name, RwMethod value, T t);
    }

    /**
     * 对象属性的封装
     */
    public static class RwMethod {
        public Field field;
        public FastMethod readMethod;
        public FastMethod writeMethod;

        public RwMethod(Field field, FastMethod readMethod, FastMethod writeMethod) {
            this.field = field;
            this.readMethod = readMethod;
            this.writeMethod = writeMethod;
        }

        public void set(Object obj, Object arg) {
            try {
                this.writeMethod.invoke(obj, new Object[]{arg});
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }

        public Object get(Object obj) {
            try {
                return this.readMethod.invoke(obj, null);
            } catch (InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 缓存
     */
    private static ConcurrentHashMap<String, LinkedHashMap<String, RwMethod>> cache = new ConcurrentHashMap<>();

    /**
     * 获取对象上的属性
     *
     * @param obj 业务读写
     * @return 属性
     */
    public static LinkedHashMap<String, RwMethod> getRWMethod(Object obj) {
        final LinkedHashMap<String, RwMethod> map = new LinkedHashMap<>();
        if (null == obj) {
            return map;
        }
        Class<?> cls = obj instanceof Class ? (Class) obj : obj.getClass();

        LinkedHashMap<String, RwMethod> mm = cache.get(cls.getName());
        if (null != mm) {
            return mm;
        }

        synchronized (cls) {
            FastClass fastClass = FastClass.create(cls);

            Class<?> clazz = cls;

            HashMap<String, PropertyDescriptor> pdMap = new HashMap<>(8);

            while (!clazz.equals(Object.class)) {

                BeanInfo beanInfo;
                try {
                    beanInfo = Introspector.getBeanInfo(clazz);
                } catch (IntrospectionException e) {
                    continue;
                }
                PropertyDescriptor[] pd = beanInfo.getPropertyDescriptors();

                for (PropertyDescriptor p : pd) {
                    pdMap.putIfAbsent(p.getName(), p);
                }

                clazz = clazz.getSuperclass();
            }
            // 合并
            {
                LinkedHashMap<String, Field> lhm = fields(cls);
                lhm.forEach((k, v) -> {
                    PropertyDescriptor p = pdMap.get(k);
                    if (null == p) {
                        return;
                    }
                    map.putIfAbsent(k, new RwMethod(v, fastClass.getMethod(p.getReadMethod()), fastClass.getMethod(p.getWriteMethod())));
                    pdMap.remove(k);
                });
                cache.putIfAbsent(cls.getName(), map);
            }
        }

        return map;
    }

    /**
     * 获取对象的所有属性
     *
     * @param cls
     * @return
     */
    private static LinkedHashMap<String, Field> fields(Class<?> cls) {

        LinkedHashMap<String, Field> map = new LinkedHashMap<>();

        synchronized (cls) {
            Class<?> clazz = cls;

            while (!clazz.equals(Object.class)) {

                Field[] fs = clazz.getDeclaredFields();

                for (Field f : fs) {
                    map.putIfAbsent(f.getName(), f);
                }

                clazz = clazz.getSuperclass();
            }
        }

        return map;

    }

    /**
     * 调用getter
     *
     * @param obj
     * @param fieldName
     * @return
     */
    public static Object getValue(Object obj, String fieldName) {
        RwMethod rw = getRWMethod(obj).get(fieldName);
        if (null != rw) {
            return rw.get(obj);
        }
        return null;
    }

    /**
     * 调用setter
     *
     * @param obj
     * @param fieldName
     * @param args
     */
    public static void setValue(Object obj, String fieldName, Object... args) {
        RwMethod rw = getRWMethod(obj).get(fieldName);
        if (null != rw) {
            rw.set(obj, args);
        }
    }

    public static String genToMap(Object object) {
        if (null == object) {
            return "";
        }
        LinkedHashMap<String, RwMethod> methodMap = getRWMethod(object);
        StringBuilder sb = new StringBuilder("HashMap<String, Object> map = new HashMap<>();");
        sb.append("\n");
        methodMap.forEach((s, rwMethod) -> {
            sb.append("map.put(\"").append(rwMethod.field.getName()).append("\", ")
                    .append("this.").append(rwMethod.field.getName()).append(");").append("\n");
        });

        return sb.toString();
    }

    public static <T> void foreach(T t, EachField<T> eachField) {
        if (null == t || null == eachField) {
            return;
        }
        HashMap<String, RwMethod> fs = getRWMethod(t);
        fs.forEach((k, v) -> eachField.deal(k, v, t));
    }

    public static String underlineToCamel(String param) {
        if (param == null || "".equals(param.trim())) {
            return "";
        }
        int len = param.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = param.charAt(i);
            if (c == '_') {
                if (++i < len) {
                    sb.append(Character.toUpperCase(param.charAt(i)));
                }
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 下划线 转驼峰<br>
     * Map --> Bean 1
     *
     * @param bean
     * @param map
     */
    public static void transMap2CamelCase2Bean(Object bean, HashMap<String, Object> map) {
        if (null == bean || null == map) {
            return;
        }
        ArrayList<Map.Entry<String, Object>> temp = new ArrayList<>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getKey().contains("_")) {
                temp.add(entry);
            }
        }
        for (Map.Entry<String, Object> entry : temp) {
            map.remove(entry.getKey());
            String key = underlineToCamel(entry.getKey());
            map.put(key, entry.getValue());
        }
        transMap2Bean(bean, map);
    }

    /**
     * Map --> Bean
     *
     * @param bean
     * @param map
     */
    public static void transMap2Bean(Object bean, HashMap<String, Object> map) {
        if (null == bean || null == map) {
            return;
        }
        try {

            getRWMethod(bean).forEach((k, v) -> {
                if (map.containsKey(k)) {
                    Object value = map.get(k);
                    v.set(bean, value);
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
            throw new IllegalArgumentException(e);
        }
    }

    /**
     * Bean --> Map
     *
     * @param bean
     * @return
     */
    public static HashMap<String, Object> transBean2Map(Object bean) {
        HashMap<String, Object> map = new HashMap<String, Object>(8);
        getRWMethod(bean).forEach((k, v) -> {
            map.put(k, v.get(bean));
        });
        return map;

    }

    /**
     * bean的属性拷贝（浅拷贝），仅拷贝不为null的属性 orig --> dest
     *
     * @param dest
     * @param orig
     * @throws IllegalArgumentException
     */
    public static <T> void merge(T dest, T orig) {
        if (null == dest || null == orig) {
            return;
        }
        getRWMethod(dest).forEach((k, v) -> {
            Object value = v.get(orig);
            if (null != value) {
                v.set(dest, value);
            }
        });
    }


}
